<?php
/**
 * Huoban.php
 * Huoban.com  接口服务
 * @author Deepseath
 * @version $Id$
 */
namespace deepseath\huoban;

use Symfony\Component\Cache\Adapter\FilesystemAdapter;

class Huoban
{
    /**
     * 缓存池
     * @var \Symfony\Component\Cache\Adapter\FilesystemAdapter
     */
    protected $_cache = null;

    /**
     * 伙伴云接口服务根 URL
     * @var string
     */
    protected $_baseUrl = '';

    /**
     * 库版本号
     * @var string
     */
    protected $_libVersion = '1.0';

    /**
     * HTTP 请求对象
     * @var object
     */
    protected $_http = null;

    /**
     * 调试模式
     * @var boolean
     */
    protected $_debug = false;

    /**
     * 企业 ID
     *
     * @var string
     */
    protected $_companyId = '';

    /**
     * 当日请求记录缓存 key
     *
     * @var string
     */
    protected $_requestNoKey = '';

    /**
     * 配置信息
     * @var array
     */
    public $config = [];

    /**
     * 单点实例
     * @param array $options
     * @return \deepseath\huoban\Huoban
     */
    public static function &instance(array $options)
    {
        static $instance = null;
        if (empty($instance)) {
            $instance = new self($options);
        }
        return $instance;
    }

    private function __construct(array $options = [])
    {
        $this->_cache = new FilesystemAdapter('huoban', 86400, null);
        if (function_exists('config')) {
            $config = config('huoban');
            if (empty($options)) {
                $options = $config;
            } else {
                $options = array_merge($config, $options);
            }
            unset($config);
        }
        $this->config = $options;
        if (!empty($options['baseUrl'])) {
            $this->_baseUrl = $options['baseUrl'];
        }
        if (isset($options['debug'])) {
            $this->_debug = $options['debug'];
        }
        if (!isset($this->config['request_log_days'])) {
            // 默认请求日志记录的天数
            $this->config['request_log_days'] = 60;
        }
        if (!isset($options['company_id'])) {
            // 企业 ID
            $this->_companyId = $options['company_id'];
        }

        $this->_http = \Yurun\Util\HttpRequest::newSession();
        $this->_requestNoKey = $this->_requestLogKey();
    }

    /**
     * 获取 ticket 信息
     * @param $force bool 是否强制获取更新 ticket
     * @return array
     * <pre>
     * + ticket     String      ticket票据，您可以将此票据缓存在您本地。重复获取ticket后，旧的ticket会失效
     * + expire_at  datetime    票据失效日期
     * + expired    int         票据生效时长，以秒为单位
     * </pre>
     */
    public function getTicket(bool $force = false) : array
    {
        $ticket = $this->_cache->getItem('ticket');
        if ($ticket->isHit() && $force === false && ($cache = $ticket->get()) && isset($cache['expire_at']) && time() < strtotime($cache['expire_at']) - 900) {
            return $ticket->get();
        }

        // 初始化 token 信息
        $newTicket = [];
        // 构造 token 获取接口地址
        $url = $this->_baseUrl . 'ticket';
        $params = [
            'application_id' => $this->config['application_id'],
            'application_secret' => $this->config['application_secret']
        ];
        // 请求接口
        $headers = [
            'Content-Type' => 'application/json;charset=utf-8'
        ];
        $this->_http->headers($headers);
        $this->log($url, '获取伙伴云 Ticket');
        $this->log($params, 'Request');
        $this->log($headers, 'Header');
        $response = $this->_http->post($url, $params, 'json');
        $newTicket = $response->json(true);
        $this->log($newTicket, 'Result');
        if (empty($newTicket) || !isset($newTicket['ticket'])) {
            throw new \Exception('request huoban ticket error' . var_export($newTicket, true) . '|' . $url . var_export($params, true), 9001);
        }

        $newTicket['_datetime'] = date('Y-m-d H:i:s');
        // 将本次获取的 ticket 值写入到缓存
        $ticket->set($newTicket);
        if (isset($newTicket['expired'])) {
            $expire = $newTicket['expired'];
        } else {
            $expire = 7200;
        }
        $ticket->expiresAfter($expire - 10);
        $this->log($ticket->get(), '保存伙伴云 Ticket');
        $this->_cache->save($ticket);

        return $newTicket;
    }

    /**
     * 接口 API POST
     * @param string $path  接口基于版本目录的路径
     * @param array $data   请求的数据
     * @param array $exendData  额外的 header 请求数据
     * @param bool $flushTicket  是否强制刷新 token
     * @throws \Exception
     * @return array|string
     */
    public function apiRequest($method, $path, $data, $exendData =[], $flushTicket = false)
    {
        static $tryCount;
        if ($tryCount === null || $flushTicket === false) {
            $tryCount = 1;
        } else {
            $tryCount++;
        }
        if ($tryCount > 3) {
            throw new \Exception('huoban SDK Error: Get ticket error, retry maximum number.');
        }
        $headers = [
            'Content-Type' => 'application/json;charset=utf-8',
            'X-Huoban-Ticket' => $this->getTicket($flushTicket)['ticket']
        ];
        if (!empty($exendData)) {
            foreach ($exendData as $_header => $_value) {
                $headers[$_header] = (string)$_value;
            }
        }

        $this->_http->headers($headers);
        $url = $this->_baseUrl . $path;
        $method = strtolower($method);

        $this->log($url, '请求接口');
        $this->log($method, 'Method');
        $this->log($headers, 'Header');
        $this->log($data, 'Request');

        // 读取请求日志缓存
        $requestLogCache = $this->_cache->getItem($this->_requestNoKey);
        $requestLog = [];
        if ($requestLogCache->isHit()) {
            $requestLog = $requestLogCache->get();
        }

        if ($method == 'post') {
            $dataString = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
            $response = $this->_http->post($url, $dataString, 'json');
            $requestLog[] = [
                'url' => $url,
                'params' => $dataString,
                'method' => 'post'
            ];
        } elseif ($method == 'get') {
            $response = $this->_http->get($url, $data);
            $requestLog[] = [
                'url' => $url,
                'params' => $data,
                'method' => 'get'
            ];
        } elseif ($method == 'put') {
            $dataString = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
            $response = $this->_http->put($url, $dataString, 'json');
            $requestLog[] = [
                'url' => $url,
                'params' => $dataString,
                'method' => 'put'
            ];
        }

        // 写入请求日志
        $requestLogCache->set($requestLog);
        $requestLogCache->expiresAfter(86400 * $this->config['request_log_days']);
        $this->_cache->save($requestLogCache);

        $body = $response->body();
        $this->log($response->getStatusCode(), 'StatusCode');
        $this->log($body, 'Body');
        $result = $response->json(true);
        $this->log($result, 'Result');
        if (!isset($result['code'])) {
            return $result;
        }
        if (!isset($result['code']) || $result['code'] != 200) {
            /*
            if (isset($result['code'])) {
                // token 无效，尝试重新刷新 token
                $this->log($tryCount, 'ticket 失效，尝试重新刷新');
                return $this->apiRequest($method, $path, $data, $exendData, true);
            }
            */
            throw new \Exception(json_encode([
                'url' => $url,
                'title' => '伙伴云接口请求错误',
                'result' => $result,
                'response' => $body,
            ], JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE), 90001);
        }

        return isset($result['data']) ? $result['data'] : [];
    }

    /**
     * 
     *
     * @return void
     */

    /**
     * 获取指定日期的请求日志
     *
     * @param string|null $date
     * @return array
     */
    public function requestLog(string $date = null) : array
    {
        $key = $this->_requestNoKey;
        if ($date !== null) {
            $key = $this->_requestLogKey($date);
        }
        $requestLog = $this->_cache->getItem($key);
        if ($requestLog->isHit()) {
            return $requestLog->get();
        }
        return [];
    }

    /**
     * 获取请求日志的缓存键名
     *
     * @param string|null $date 请求日期，格式为：YYYYMMDD
     * @return string
     */
    protected function _requestLogKey(string $date = null) : string
    {
        if ($date === null) {
            $date = date('Ymd');
        }
        return 'request_log_' . $date;
    }

    /**
     * 打印调试日志
     * @param mixed $info
     */
    public function log($info, $title = '', $level = 'huoban')
    {
        if ($this->_debug) {
            $info = is_scalar($info) ? $info : json_encode($info, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
            $title = $title ? "[{$title}]" : '';
            \think\facade\Log::write($title . $info, 'huoban');
        }
    }

    /**
     * 获取配置信息
     * @return array
     */
    public function config()
    {
        return $this->config;
    }
}
